# coding:utf-8

import requests
from bs4 import BeautifulSoup
import pickle
import time
import matplotlib.pyplot as plt
import pandas
import re

# 访问链接，并返回页面信息
def get_page(url, headers="", params=""):
    # 访问链接，拿到返回结果
    page = requests.get(url, headers=headers, params=params)
    # 获取页面的源代码，并将源代码解析为beautifulsoup
    return (page.text, BeautifulSoup(page.text, 'html.parser'))

# 判断页面是否有下一页
def page_has_next(soup):
    # 获取下一页被禁用区域
    try:
        # 获取下一页被禁用的区域
        is_last_page = soup.select(".next.hidden")[0]
        # 如果获取到，则说明已到最后一页，没有下一页了
        return False
    # 如果出错，则说明还有下一页
    except:
        return True

# 获取招聘工作详细页的链接
def get_job_urls(soup):
    # 创建一个链接空列表
    job_urls = []
    # 从页面中获取招聘工作链接所在区域
    job_url_items = soup.select("div.job > div.name > a")
    # 变遍区域中的每一条信息
    for item in job_url_items:
        # 将链接存储到列表中
        job_urls.append(item.get("href"))
    # 返回当前页中所有的招聘工作详细链接列表
    return job_urls

# 获取招聘工作的详细信息
def get_job(url):
    # 访问招聘工作的详细信息页，并解析为beautifulsoup
    soup = get_page(url, headers=my_headers)[1]
    # 创建记录招聘信息的空字典
    job = {}
    # 从招聘工作相信信息页获取招聘工作的名称，如果出错，就记为"无"
    try:
        job_name = soup.select("h2")[0].contents[0].strip()
    except:
        job_name = "无"
    job["名称"] = job_name
    # 从招聘工作相信信息页获取招聘工作的薪资，如果出错，就记为"无"
    try:
        salary = soup.select("h2 > span")[0].text.strip()
    except:
        salary = "无"
    job["工资"] = salary
    # 从招聘工作相信信息页获取招聘工作的其他信息区域, 如果出错，就跳过
    try:
        job_info_items = soup.select("div.text > ul.half-list")[0].select("li")
        # 遍历其他信息区域
        for item in job_info_items:
            # 将招聘工作的其他信息记录到字典里
            job[item.contents[0].strip().replace("：","")] = item.select("span")[0].text.strip()
    except Exception as e:
        print(e)
    # 将招聘工作详细信息页的链接记录到字典里
    job["链接"] = url
    return job

# 将列表转换为csv，存储到本地
def list_to_csv(list):
    contents = ""
    # 将列表中字典的key作为csv的表头
    for key in list[0].keys():
        contents += (key.replace(':','') + ",")

    # 将列表中字典的value作为csv的表格正文
    for item in list:
        contents += "\n"
        for key in item.keys():
            contents += (item[key].replace(",", "，") + ",")

    # 将csv文件保存到本地
    write_file("招聘信息列表.csv", contents)

# 在本地写文件
def write_file(path, content):
    # 在指定路径下创建文件
    f = open(path, 'w', encoding='utf-8-sig')
    # 将信息写入到文件中
    f.write(content)
    # 关闭文件
    f.close()

# 在本地写对象
def write_object(path, object):
    # 在指定路径下创建文件
    file = open(path, 'wb')
    # 将对象写入到文件中
    pickle.dump(object, file, 2)
    # 关闭文件
    file.close()

# 从本地读取对象
def read_object(path):
    # 打开文件
    file = open(path, 'rb')
    # 将文件中内容加载到内存
    object = pickle.load(file)
    # 关闭文件
    file.close()
    # 返回对象
    return object

# 画条形图
def draw_bar(x, name, top_N = -1):
    # 创建一个空字典
    dict = {}
    # 遍历列表x
    for item in x:
        # 如果列表中的元素已经在字典中，那么字典中对应元素的出现次数+1.jpg
        if item in dict.keys():
            dict[item] += 1
        # 如果列表中的元素不在字典中，那么在字典中添加对应元素，并将其出现次数设为1
        else:
            dict[item] = 1
    # 对字典进行排序
    dict = sorted(dict.items(), key=lambda x: x[1], reverse=True)
    # 创建记录项目的字典
    keys = []
    # 创建记录项目出现次数的字典
    values = []
    # 遍历排序后的字典，将项目和次数分别提取出来
    for item in dict:
        keys.append(item[0])
        values.append(item[1])

    # 设置Top_N的值
    if top_N != -1:
        keys = keys[:top_N]
        values = values[:top_N]

    # 设置图片大小和清晰度
    plt.figure(figsize=(4,2),dpi=300)
    # 设置中文字体
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
    # 设置坐标轴文字大小
    plt.tick_params(labelsize=2)
    # 设置标题
    plt.title(name)
    # 绘制条形图
    plt.bar(keys, values)
    # 设置条形图中显示的数据
    for a, b in zip(keys, values):
        plt.text(a, b + 0.05, '%.0f' % b, ha='center', va='bottom', fontsize=4)
    # 将条形图保存到本地
    plt.savefig(name + ".png")
    # 显示条形图
    plt.show()

# 画饼图
def draw_pie(x, name):
    # 创建一个空字典
    dict = {}
    # 遍历列表x
    for item in x:
        # 如果列表中的元素已经在字典中，那么字典中对应元素的出现次数+1.jpg
        if item in dict.keys():
            dict[item] += 1
        # 如果列表中的元素不在字典中，那么在字典中添加对应元素，并将其出现次数设为1
        else:
            dict[item] = 1
    # 对字典进行排序
    dict = sorted(dict.items(), key=lambda x: x[1], reverse=True)
    # 创建记录项目的字典
    keys = []
    # 创建记录项目出现次数的字典
    values = []
    # 遍历排序后的字典，将项目和次数分别提取出来
    for item in dict:
        keys.append(item[0])
        values.append(item[1])

    # 设置图片大小和清晰度
    plt.figure(figsize=(4,2),dpi=300)
    # 设置中文字体
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
    # 设置坐标轴文字大小
    plt.tick_params(labelsize=2)
    # 设置标题
    plt.title(name)
    # 设定各项距离圆心n个半径
    explode = [0.05 for _ in range(len(keys))]
    # 绘制饼图
    patches, l_text, p_text = plt.pie(values, explode=explode, labels=keys, autopct='%.1f%%')
    # 遍历饼图上数字，设置大小
    for t in p_text:
        t.set_size(4)
    # 遍历图例文字，设置大小
    for t in l_text:
        t.set_size(4)
    # 将条形图保存到本地
    plt.savefig(name + ".png")
    # 显示条形图
    plt.show()

# 绘制直方图
def draw_histogram(x, name):
    # 设置图片大小和清晰度
    plt.figure(figsize=(4, 2), dpi=300)
    # 设置中文字体
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']

    # 单独绘制直方图
    plt.hist(x, bins=50, color = 'steelblue', edgecolor = 'black')

    # 设置坐标轴文字大小
    plt.tick_params(labelsize=3)
    # 添加标题
    plt.title(name)
    # 保存图形
    plt.savefig(name + ".png")
    # 显示图形
    plt.show()

if __name__ == '__main__':
    # 设置主页链接
    main_url = 'https://www.hljbys.org.cn/job'
    # 设置多页的基础链接
    page_base_url = 'https://www.hljbys.org.cn/job/index?page='

    # 设置请求头，从浏览器中复制出相关信息，其中最重要的是cookie和user-agent
    my_headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q1=0.9,image/webp,image/apng,*/*;q1=0.8,application/signed-exchange;v=b3;q1=0.9',
        'accept-encoding': 'gzip, deflate, br',
        'accept-language': 'zh-CN,zh;q1=0.9,en;q1=0.8',
        'cache-control': 'max-age=0',
        'cookie': 'PHPSESSID=gemv1rq13hu2p295cr2tq6g4u3; SL_GWPT_Show_Hide_tmp=1.jpg; SL_wptGlobTipTmp=1.jpg',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'none',
        'sec-fetch-user': '?1.jpg',
        'upgrade-insecure-requests': '1.jpg',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36',
    }

    # 获取主页的内容，并解析为beautifulsoup
    main_page_soup = get_page(url=main_url, headers=my_headers)[1]

    # 创建存储招聘工作的链接的列表
    total_job_urls = []
    # 默认有下一页
    has_next = True
    # 设置初始页码
    current_page_num = 1

    # 当还有下一页时，就继续循环获取招聘工作的链接
    while(has_next):
        # 打印当前进度
        print("第" + str(current_page_num) + "页")
        # 获取当前页的链接
        page_url = page_base_url + str(current_page_num)
        # 将当前页的内容解析为beautifulsoup
        page_soup = get_page(page_url, headers=my_headers)[1]
        # 将当前页中包含的招聘工作链接加入到列表中
        total_job_urls += get_job_urls(page_soup)
        # 将招聘工作链接列表保存到本地
        write_object("job_urls.pkl", total_job_urls)
        # 页码+1.jpg
        current_page_num += 1
        # 判断当前页是否有下一页
        has_next = page_has_next(page_soup)
        # 避免频繁访问，每访问100次暂停5秒
        if(current_page_num % 100 == 0):
            time.sleep(5)

    # 创建工作信息列表
    jobs = []
    # 从本地读取招聘工作详细信息链接列表
    total_job_urls = read_object("job_urls.pkl")

    # 遍历链接列表中的每一条链接
    for i in range(len(total_job_urls)):
        # 打印进度
        print("第" + str(i+1) + "条/共" + str(len(total_job_urls)) + "条")
        # 设置招聘工作详情页的链接
        job_page_url = 'https://www.hljbys.org.cn' + total_job_urls[i]
        # 获取招聘工作的信息
        job = get_job(job_page_url)
        # 将招聘工作的信息加入到列表中
        jobs.append(job)
        # 将招聘工作信息的列表保存到本地
        write_object("job_infos.pkl", jobs)

    # 将招聘工作详细信息的列表转换为csv，保存到奥本地
    list_to_csv(jobs)

    # 读取本地存储的招聘工作信息列表
    jobs = read_object("job_infos.pkl")

    # 创建学历的空列表
    xuelis = []
    # 遍历招聘工作列表，读取所有的学历信息
    for job in jobs:
        # 如果招聘工作信息中有学历要求，则将招聘工作的学历信息加入到学历列表中
        if "学历要求" in job.keys():
            xuelis.append(job['学历要求'])

    # 绘制学历饼图
    draw_pie(xuelis, "招聘工作学历要求分布图")

    # 创建工资的空列表
    salarys = []
    # 遍历招聘工作列表，读取所有的工资信息
    for job in jobs:
        # 如果招聘工作信息中有工资要求，则将招聘工作的薪资信息加入到工资列表中
        if "工资" in job.keys():
            salarys.append(job["工资"])

    # 绘制薪资条形图
    draw_bar(salarys, "招聘工作薪资分布图")

    # 创建地点的空列表
    cities = []
    # 遍历招聘工作列表，读取所有的工资信息
    for job in jobs:
        # 如果招聘工作信息中有工作城市，则将招聘工作的工作城市信息加入到工资列表中
        if "工作城市" in job.keys():
            # 剔除工作城市为"其他"的数据
            if job["工作城市"] != "其他":
                cities.append(job["工作城市"])
    # 绘制工作城市分布条形图
    draw_bar(cities, "招聘工作城市Top10分布图", top_N = 10)

    # 创建工作性质的空列表
    work_types = []
    # 遍历招聘工作列表，读取所有的工作性质信息
    for job in jobs:
        # 如果招聘工作信息中有工作性质，则将招聘工作的工作性质信息加入到工作性质列表中
        if "工作性质" in job.keys():
            # 剔除工作城市为"--请选择--"的数据
            if job["工作性质"] != "--请选择--":
                work_types.append(job['工作性质'])

    # 绘制工作性质饼图
    draw_pie(work_types, "招聘工作的工作性质分布图")

    # 创建招聘人数的空列表
    employees = []
    # 遍历招聘工作列表，读取所有的招聘人数信息
    for job in jobs:
        # 如果招聘工作信息中有招聘人数，则将招聘工作的招聘人数信息加入到工作性质列表中
        if "招聘人数" in job.keys():
            # 提取其中的人数，转换为整数，存入列表中
            employees.append(int(job['招聘人数'].split("人")[0]))

    # 绘制招聘人数直方图
    draw_histogram(employees, "招聘人数的直方图")

    # 创建语言的空列表
    languages = []
    # 遍历招聘工作列表，读取所有的语言要求信息
    for job in jobs:
        # 如果招聘工作信息中有语言要求，则将招聘工作的语言要求信息加入到语言要求中
        if "语言要求" in job.keys():
            languages.append(job['语言要求'])

    # 绘制招聘人数直方图
    draw_bar(languages, "招聘工作的语言要求分布图")